package com.cmw.socket.handler;

import com.cmw.socket.core.Connector;
import com.cmw.socket.utils.CloseUtils;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 客户端处理器
 * @author chengmingwei
 * @date 2020-06-25 17:46
 */
@Slf4j
public class ClientHandler {

    private final Connector connector;

    private final SocketChannel socketChannel;

    private final ClientWriteHandler writeHandler;

    private final ClientHandlerCallback clientHandlerCallback;

    private final String clientInfo;

    public ClientHandler(SocketChannel socketChannel, ClientHandlerCallback clientHandlerCallback) throws IOException {
        this.socketChannel = socketChannel;
        connector = new Connector(){
            @Override
            public void onChannelClosed(SocketChannel channel) {
                super.onChannelClosed(channel);
                exitBySelf();
            }

            @Override
            protected void onReceiveNewMessage(String str) {
                super.onReceiveNewMessage(str);
                clientHandlerCallback.onNewMessageArrived(ClientHandler.this, str);
            }
        };
        connector.setup(socketChannel);
        Selector writeSelector = Selector.open();
        socketChannel.register(writeSelector, SelectionKey.OP_WRITE);
        this.writeHandler = new ClientWriteHandler(writeSelector);
        this.clientHandlerCallback = clientHandlerCallback;
        this.clientInfo = socketChannel.getRemoteAddress().toString();
        log.info("新客户端连接：{}",clientInfo);
    }


    public String getClientInfo(){
        return clientInfo;
    }

    public void exit(){
        CloseUtils.close(connector);
        writeHandler.exit();
        CloseUtils.close(socketChannel);
        log.info("客户端已退出：{}", clientInfo);
    }

    public void send(String str){
        writeHandler.send(str);
    }

    private void exitBySelf(){
        exit();
        clientHandlerCallback.onSelfClosed(this);
    }

    public interface ClientHandlerCallback{
        //自身关闭通知
        void onSelfClosed(ClientHandler handler);

        //收到消息时通知
        void onNewMessageArrived(ClientHandler handler, String msg);
    }

    class ClientWriteHandler{
        private boolean done = false;

        private final Selector selector;

        private final ByteBuffer byteBuffer;

        private final ExecutorService executorService;

        public ClientWriteHandler(Selector selector) {
            this.selector = selector;
            this.byteBuffer = ByteBuffer.allocate(256);
            this.executorService = Executors.newSingleThreadExecutor();
        }

        void exit(){
            done = true;
            CloseUtils.close(selector);
            executorService.shutdown();
        }

        void send(String str){
            if(done) return;
            executorService.execute(new WriteRunnable(str));
        }

        class WriteRunnable implements Runnable{
            private final String msg;

            public WriteRunnable(String msg) {
                this.msg = msg + '\n';
            }

            @Override
            public void run() {
                if(ClientWriteHandler.this.done){
                    log.error("ClientWriteHandler done = true,任务已处理完");
                    return;
                }

                byteBuffer.clear();
                byteBuffer.put(msg.getBytes());
                // 反转操作，重点 flip : 把limit设为当前position，把position设为0，一般在从Buffer读出数据前调用。
                byteBuffer.flip();
                // hasRemaining 返回 limit - position  返回剩余的可用长度
                while (!done && byteBuffer.hasRemaining()){
                    try {
                        int len = socketChannel.write(byteBuffer);
                        // len = 0 合法
                        if(len < 0){
                            log.info("客户端已无法发送数据！");
                            ClientHandler.this.exitBySelf();
                            break;
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}
